/* 
 * File:   Images.cpp
 * Author: ungerma
 * 
 * Created on 04. August 2014, 14:15
 */

#include "Images.h"
#include "FeatureVectors.h"

using namespace cv;

Images::Images(HOGSettings settings):settings(settings) {
    
}

void Images::readImagesInDirNoFeatureVecotrs(vector <SingleImage>& res, string dirName,
        ImageParsing p, float groundTruth, string nameFile) {
    
    DIR *dir;
    struct dirent *ent;
    SingleImage si;
    Mat image;
    ofstream file;
    
    file.open(nameFile.c_str(), file.app);
    if(!file.is_open()) {
        throw runtime_error("Could not open " + nameFile + " for writing");
    }
    
    if ((dir = opendir (dirName.c_str())) != NULL) {
       
        while ((ent = readdir (dir)) != NULL) {
        
            if (ent->d_type & DT_DIR) {
                continue;
            }
                                   
            string absolutePath = dirName + ent->d_name;
           
            // Skip annotation files.
            if(absolutePath.find(".txt") != -1) {
                continue;
            }
            cout << "Reading " << absolutePath << " without feature vectors" << endl;      
            
            if(p == N_RANDOM_WINDOWS) {
                for(int i=0; i<NUMBER_RANDOM_WINDOWS; ++i) {
                    si = SingleImage(image, absolutePath, groundTruth,
                        vector<ImageWindow>());
                    res.push_back(si); 
                    file << absolutePath << endl;
                }
   
            }
            else if(p == SCALE_SPACE_WINDOW) {
                perror ("");
                throw runtime_error("Not implemented. Please delete all feature vector files");
            }
            else {
                si = SingleImage(image, absolutePath, groundTruth,
                        vector<ImageWindow>());
                res.push_back(si); 
                file << absolutePath << endl;
            }
            
        }
        closedir (dir);
        file.close();
    }
    else {
        perror ("");
        throw runtime_error("Could not read all images: IO error");
    }    
}

void Images::readImagesInDir(vector <SingleImage>& res,
        string dirName, enum ImageParsing p, float groundTruth, 
        string featureFile, string mpeg7List, string mpeg7Path, string mpegScd, 
        string mpegEhd, string nameFile) {

    srand(RANDOM_SEED); // For repeatable experiments.

    DIR *dir;
    struct dirent *ent;
    FeatureVectors fvs(settings);
    SingleImage si;
    ofstream file;
    
    file.open(nameFile.c_str(), file.app);
    if(!file.is_open() && nameFile != "") {
        throw runtime_error("Could not open " + nameFile + " for writing");
    }
    
    if ((dir = opendir (dirName.c_str())) != NULL) {
       
        while ((ent = readdir (dir)) != NULL) {
        
            if (ent->d_type & DT_DIR) {
                continue;
            }
                                   
            string absolutePath = dirName + ent->d_name;
           
            // Skip annotation files.
            if(absolutePath.find(".txt") != -1) {
                continue;
            }
            
            cout << "Reading " << absolutePath << endl;
            Mat image = imread(absolutePath, 1);
            
            if(image.empty()) {
                throw runtime_error("Could not read all images. Problem with: " 
                        + absolutePath);
            }
            
            /*
             * Choose random windows from (too large) image.
             */
            if(p == N_RANDOM_WINDOWS) {
                
                vector<ImageWindow> windows;
                
                for(int j=0; j<NUMBER_RANDOM_WINDOWS; ++j) {
                    
                    Size s = image.size();                    
                    int x = rand() % (s.width  - POS_TRAINING_WIDTH)  + 1;
                    int y = rand() % (s.height - POS_TRAINING_HEIGHT) + 1;                    
                    if(x < 1 || y < 1) { 
                        throw runtime_error(
                                "Image too small - will not scale up: " 
                                + absolutePath);
                    }
                    
                    Rect subWindow(x, y, POS_TRAINING_WIDTH, POS_TRAINING_HEIGHT);
                    exceptionIfWrongSize(subWindow, absolutePath);
                    windows.push_back(ImageWindow(subWindow));
                    
                }
                si = SingleImage(image, absolutePath, groundTruth, windows);               
                
            }
            
            /*
             * Assume images have training size + margin.
             * If we crop the margin away, the images should have the correct
             * size. 
             */
            else if(p == CROP_FROM_MARGIN) {
                
                Rect subWindow(POS_TRAINING_MARGIN, POS_TRAINING_MARGIN, 
                               POS_TRAINING_WIDTH, POS_TRAINING_HEIGHT);
                image = image(subWindow);
                exceptionIfWrongSize(image, absolutePath);           
                si = SingleImage(image, absolutePath, groundTruth,
                    vector<ImageWindow>()); 
                file << absolutePath << endl;
                
            }
            
            /*
             * Assume images have correct size.
             */
            else if(p == UNCHANGED) {
                
                exceptionIfWrongSize(image, absolutePath);           
                si = SingleImage(image, absolutePath, groundTruth,
                    vector<ImageWindow>()); 
                file << absolutePath << endl;
            }
            
            /*
             * Search for annotation file. Crop out first annotated person.
             * Resize to correct training size.
             */
            else if(p == RESIZE_TO_ANNOTATION) {
                
                image = resizeToAnnotation(image, ent->d_name, dirName);
                exceptionIfWrongSize(image, absolutePath);
                si = SingleImage(image, absolutePath, groundTruth,
                    vector<ImageWindow>()); 
                file << absolutePath << endl;
            }
            
            /*
             * A sound way to take any image size as test data.
             */
            else if(p == SCALE_SPACE_WINDOW) {
                
                si = SingleImage(image, absolutePath, groundTruth,
                    vector<ImageWindow>());
                addScaleSpaceWindows(&si, image);

            }
            
            // Remember image with all its detection windows for later.
            res.push_back(si);
            
            /*
             * Note that we do not store the windows (=scaled sub images) again.
             * We set a pointer to the original image here. With rectangle
             * and pyramid level, we could retrieve them again later.
             */
            for(int i=0; i < res.back().getWindows()->size(); ++i) {
                res.back().getWindows()->at(i).setParent(&res.back());
                file << absolutePath << endl;
            }
            
            // Calculate feature vectors for image with all its detection 
            // windows.
            writeFilelist(absolutePath, mpeg7List);
            fvs.calculateAndSerialize(res.back(), groundTruth, featureFile,
                mpeg7List, mpeg7Path, mpegScd, mpegEhd);
            
            // We do not need the image matrix per se anymore. If we need it 
            // again, we have to reload with the filename.
            si.releaseMatrix();
            res.back().releaseMatrix();

        }
    closedir (dir);
    file.close();
    }
    else {
        perror ("");
        throw runtime_error("Could not read all images: IO error");
    }
            
}

void Images::debugWindow(Mat image, string absolutePath) {
    
    // Enable debug image output here!
    if(false) {
        string name = "Debug Image";
        cout << "Size " << image.size().width << "x" << 
            image.size().height << endl;
        cout << "Location " << absolutePath << endl;
        namedWindow(name, WINDOW_AUTOSIZE);
        imshow(name, image);
        waitKey(0);
        destroyWindow(name);
    }
    
}

void Images::addScaleSpaceWindows(SingleImage* si, Mat orig) {
    
    Mat image = orig;
    int width = orig.size().width, height = orig.size().height, 
            slideX, slideY, pyramidLevel = 0, 
            noWindows = 0;
    
    slideX = SLIDING_WINDOW_X;
    slideY = SLIDING_WINDOW_Y;
    width = image.size().width;
    height = image.size().height;
    
    do {
        
        if(width  < POS_TRAINING_WIDTH  ||
           height < POS_TRAINING_HEIGHT   ) {
            break;
        }

        for(int x=1; x <= width-POS_TRAINING_WIDTH; x+= slideX) {
            
            for(int y=1; y <= height-POS_TRAINING_HEIGHT; y += slideY) {

                Rect subWindow(x, y, POS_TRAINING_WIDTH, POS_TRAINING_HEIGHT);
                ImageWindow iw(subWindow, pyramidLevel);
                si->addWindow(iw);
                debugWindow(iw.getData(), "added normally");
                exceptionIfWrongSize(subWindow, si->getFilename());
                ++noWindows;
                
                if(x == 1) {
                    
                    /*
                     * Add image at the very right to prevent boundary conditions at 
                     * high step sizes.
                     */
                    Rect subWindow(width - POS_TRAINING_WIDTH, y, POS_TRAINING_WIDTH,
                            POS_TRAINING_HEIGHT);
                    ImageWindow iw( subWindow, pyramidLevel);
                    si->addWindow(iw);
                    debugWindow(iw.getData(), "added edge 1");
                    exceptionIfWrongSize(subWindow, si->getFilename());
                    ++noWindows;

                }

            }

            /*
             * Add image at the very bottom to prevent boundary conditions at 
             * high step sizes.
             */             
            Rect subWindow(x, height - POS_TRAINING_HEIGHT, POS_TRAINING_WIDTH,
                    POS_TRAINING_HEIGHT);
            ImageWindow iw(subWindow, pyramidLevel);
            si->addWindow(iw);
            debugWindow(iw.getData(), "added edge 2");
            exceptionIfWrongSize(subWindow, si->getFilename());
            ++noWindows;

        }
        
        width /= SCALE_SPACE_FACTOR;
        height /= SCALE_SPACE_FACTOR;
        slideY = max(1, (int) (slideY/Images::SCALE_SPACE_FACTOR));
        slideX = max(1, (int) (slideX/Images::SCALE_SPACE_FACTOR));
        --pyramidLevel;        
        
    } while( 1 );     
    
    cout << "Added " << noWindows << " window(s)" << endl;
    
}

Mat Images::resizeToAnnotation(Mat i, string relPath, string dir) {
    
    int where = relPath.find_last_of(".");
    if(where == -1) {
        throw runtime_error("Invalid file extension for " 
                + relPath);
    }
    relPath = relPath.substr(0, where );
    relPath = relPath.append(".txt");
    string annotation(dir + relPath);
    
    ifstream file;
    string line;  
    int xmin, ymin, xmax, ymax, dirty = 0;
    string parse;
    
    file.open(annotation.c_str());
    if(!file.is_open()) {
        
        // Try to find outer annotations
        cout << "No annotation found " << annotation << endl;
        
        // No annotation. Resize to default.
        xmin = BOUNDING_MARGIN;
        ymin = BOUNDING_MARGIN;
        xmax = POS_TRAINING_WIDTH  + BOUNDING_MARGIN;
        ymax = POS_TRAINING_HEIGHT + BOUNDING_MARGIN;
        
        dirty = 1;
                
    }
    
    while(file.is_open() && !file.eof()) {
        getline(file, line);
        
        string search(
        "Bounding box for object 1 \"PASperson\" (Xmin, Ymin) - (Xmax, Ymax) : (");
        int found = line.find(search);
        if(found != -1) {
            
            line = line.substr(found + search.length());
            // 530, 113) - (742, 581)
            
            found = line.find(",");
            parse = line.substr(0, found);
            xmin = atoi(parse.c_str());
            line = line.substr(found + 2);
            
            found = line.find(")");
            parse = line.substr(0, found);
            ymin = atoi(parse.c_str());
            line = line.substr(found + 5);
            
            found = line.find(",");
            parse = line.substr(0, found);
            xmax = atoi(parse.c_str());
            line = line.substr(found + 2);
            
            found = line.find(")");
            parse = line.substr(0, found);
            ymax = atoi(parse.c_str());
            
            dirty = 1;
        }
    }
    
    file.close();
    if(file.is_open()) {
        throw runtime_error("Could not close " + annotation);
    }
    if(!dirty) {
        throw runtime_error("Invalid annotation file " + annotation);
    }

    /*
     * The person bounding boxes in INRIA are too narrow.
     * We make them wider here.
     */
    xmin -= BOUNDING_MARGIN;
    if(xmin < 1) {
        xmin = 1;
    }
    xmax += BOUNDING_MARGIN;
    if(xmax > i.size().width) {
        xmax = i.size().width;
    }
    ymin -= BOUNDING_MARGIN;
    if(ymin < 1) {
        ymin = 1;
    }
    ymax += BOUNDING_MARGIN;
    if(ymax > i.size().height) {
        ymax = i.size().height;
    }
    
    // Perform actual resize.
    Rect subWindow(xmin, ymin, xmax - xmin, ymax - ymin);
    i = i(subWindow);
    Mat ret;
    resize(i, ret, Size(POS_TRAINING_WIDTH, POS_TRAINING_HEIGHT));
    return ret;
    
}

void Images::exceptionIfWrongSize(Rect r, String absolutePath) {
    
    if(r.width  != POS_TRAINING_WIDTH ||
       r.height != POS_TRAINING_HEIGHT) {
        
        throw runtime_error("Images size problem with: " + absolutePath);
        
    }
    
}

void Images::exceptionIfWrongSize(Mat i, String absolutePath) {
    
    Size s = i.size();
    if(s.width  != POS_TRAINING_WIDTH ||
       s.height != POS_TRAINING_HEIGHT) {
        
        throw runtime_error("Images size problem with: " + absolutePath);
        
    }
    
}

void Images::readTrain(String posTrainPath, String negTrainPath, 
        String featureFile, string mpeg7List, string mpeg7Path, string mpegScd, 
        string mpegEhd) {
    
    ImageParsing pPos = CROP_FROM_MARGIN;
    ImageParsing pNeg = N_RANDOM_WINDOWS;
        
    unlink(mpeg7List.c_str());
    // Better than checking return code if file was never there.
    if(access(mpeg7List.c_str(), F_OK) != -1) {
        throw runtime_error("Could not delete " + mpeg7List);
    }
    
    readImagesInDir(posTrain, posTrainPath, pPos,
            TestResults::CLASS_A, featureFile, mpeg7List, mpeg7Path, mpegScd, 
                mpegEhd, "");
    readImagesInDir(negTrain, negTrainPath, pNeg,
            TestResults::CLASS_B, featureFile, mpeg7List, mpeg7Path, mpegScd, 
                mpegEhd, "");
    
}

void Images::readTest(String posTestPath, String negTestPath,
        String featureFile, string mpeg7List, string mpeg7Path, string mpegScd, 
        string mpegEhd, string nameFile) {
    
    ImageParsing pPos = CROP_FROM_MARGIN;
    ImageParsing pNeg = N_RANDOM_WINDOWS;
    
    // Ignore - maybe it was not there.
    unlink(nameFile.c_str());
    
    // Feature vectors exist already. Save time - skip.
    // But we need the file names and class labels. Read them, but do not
    // generate feature vectors.
    if(access(featureFile.c_str(), F_OK) != -1) {
        readImagesInDirNoFeatureVecotrs(posTest, posTestPath,
            pPos, TestResults::CLASS_A, nameFile);
        readImagesInDirNoFeatureVecotrs(negTest, negTestPath,
            pNeg, TestResults::CLASS_B, nameFile);
        return;
        
    }
    unlink(mpeg7List.c_str());
    // Better than checking return code if file was never there.
    if(access(mpeg7List.c_str(), F_OK) != -1) {
        throw runtime_error("Could not delete " + mpeg7List);
    }
    
    // For quick experimentation: Set both to  CROP_FROM_MARGIN.
    // For full experiments: Set both to SCALE_SPACE_WINDOW.
    readImagesInDir(posTest, posTestPath, pPos,
            TestResults::CLASS_UNKNOWN, featureFile, mpeg7List, mpeg7Path, 
                mpegScd, mpegEhd, nameFile);
    readImagesInDir(negTest, negTestPath, pNeg,
            TestResults::CLASS_UNKNOWN, featureFile, mpeg7List, mpeg7Path, 
                mpegScd, mpegEhd, nameFile);
    
}

void Images::writeFilelist(string filename, string targetfile) {
    
    ofstream file;   
    file.open(targetfile.c_str(), file.trunc);
    if(!file.is_open()) {
         throw runtime_error("Could not open " + targetfile + " for writing");
    }
    file << filename;
    file.close();
    if(file.is_open()) {
        throw runtime_error("Could not close " + targetfile);
    }
    
}

vector<SingleImage> Images::getPosTrain() {
    return posTrain;
}

vector<SingleImage> Images::getNegTrain() {
    return negTrain;
}

vector<SingleImage> Images::getPosTest() {
    return posTest;
}

vector<SingleImage> Images::getNegTest() {
    return negTest;
}
